package com.schedualnew.mq.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.json.JSONUtil;
import com.ewell.sdk.business.EwellServiceTool;
import com.ewell.sdk.business.MessageHandler;
import com.ewell.sdk.domain.MessageEntity;
import com.ewell.sdk.exception.SDKException;
import com.schedualnew.mq.config.AppParamsConfig;
import com.schedualnew.mq.dto.common.apply.ChangeExaminationStatusRequestVo2;
import com.schedualnew.mq.dto.esb.notice.WechatNoticeResponse;
import com.schedualnew.mq.dto.exam.ExamApplyDto;
import com.schedualnew.mq.dto.exam.ExamApplyItemDto;
import com.schedualnew.mq.mapper.ExamApplyItemMapper;
import com.schedualnew.mq.mapper.ExamApplyMapper;
import com.schedualnew.mq.mapper.ExamScheduleItemMapper;
import com.schedualnew.mq.mapper.exam.ExamReferMapper;
import com.schedualnew.mq.util.CommonUtil;
import com.schedualnew.mq.util.JacksonUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

@Service
public class MQClientService {

    private Logger logger = LogManager.getLogger(MQClientService.class);

    ThreadPoolExecutor executor;

    private Map<String, Object> listenMap = new HashMap<>();

    @Autowired
    private EwellServiceTool ewellServiceTool;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private AppParamsConfig appParamsConfig;

    @Autowired
    private ExamApplyMapper examApplyMapper;

    @Autowired
    private ExamReferMapper examReferMapper;

    @Autowired
    private ExamApplyItemMapper examApplyItemMapper;

    @Autowired
    private ExamScheduleItemMapper examScheduleItemMapper;


    /**
     * 推送消息到MQ
     *
     * @return
     */
    public String pushMsg(String queueName, String channelId, String msg) {
        logger.info("pushMsg参数:{},{},{}", queueName, channelId, msg);
        try {
            String cid = null;
            String uuid = UUID.randomUUID().toString().replace("-", "");
            logger.info("composePutMsgWithId参数:{},{},{},{}", cid, channelId, msg, uuid);

            String putMsg = ewellServiceTool.composePutMsg(queueName, channelId, msg);
            logger.info("composePutMsg:{}", putMsg);

            return "0";
        } catch (SDKException e) {
            logger.error("pushMsg:error!", e);
            return "-1:" + e.getMessage();
        }
    }

    /**
     * 推送消息并获取返回
     *
     * @param queueName
     * @param channelId
     * @param msg
     * @return
     */
    public String pushMsgWithResultMsgId(String queueName, String channelId, String msg) {
        logger.info("pushMsgWithResultMsgId:{},{},{}", queueName, channelId, msg);
        try {
            String putMsg = ewellServiceTool.composePutMsg(queueName, channelId, msg);
            logger.info("composePutMsg:{}", putMsg);

            return putMsg;
        } catch (SDKException e) {
            logger.error("pushMsgWithResultMsgId:error!", e);
            return "-1:" + e.getMessage();
        }
    }


    public String pushMsgWithResult(String queueName, String channelId, String msg) {
        MessageEntity messageEntity = pushMsgWithResult2(queueName, channelId, msg);
        return JacksonUtil.toJsonString(messageEntity);
    }

    public MessageEntity pushMsgWithResult2(String queueName, String channelId, String msg) {
        logger.info("pushMsgWithResult参数:{},{},{}", queueName, channelId, msg);
        try {
            MessageEntity messageEntity = ewellServiceTool.composePutAndGetMsg(queueName, channelId, 10000, msg);
            logger.info("pushMsgWithResult:返回结果:{}", JacksonUtil.toJsonString(messageEntity));
            return messageEntity;
        } catch (SDKException e) {
            logger.error("pushMsgWithResult:异常!" + e.getMessage(), e);
        }
        return null;
    }

    /**
     * 根据消息id获取列表数据
     *
     * @param queueName
     * @param channelId
     * @param msgId
     * @return
     */
    public MessageEntity getMsgWithMsgId(String queueName, String channelId, String msgId) {
        logger.info("getMsgWithMsgId:{},{},{}", queueName, channelId, msgId);
        try {
            MessageEntity messageEntity = ewellServiceTool.composeGetMsgById(queueName, channelId, 1000, msgId);
            // logger.info("composePutMsg:{}", JacksonUtil.toJsonString(messageEntity));
            return messageEntity;
        } catch (SDKException e) {
            logger.error("pushMsgWithResult:error!", e);
        }
        return null;
    }

    /**
     * 推送微信消息
     *
     * @param requestVo
     * @return
     */
    public String pushWechatMsg(ChangeExaminationStatusRequestVo2 requestVo) {
        //  封装参数，千万不要替换为Map与HashMap，否则参数无法传递
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();

        //  也支持中文 soap_method:SendNotice
        params.add("soap_method", "SendNotice");
        try {
            ExamApplyDto examApply = examScheduleItemMapper.queryScheduledInfo(requestVo.getApplyNo());
            if (Objects.isNull(examApply)) {
                logger.error("推送微信消息失败,没有找到申请信息:{}", requestVo.getApplyNo());
                return "-1:推送微信消息失败,没有找到申请信息";
            }
            // 预约需要时间
            if ("0,1".contains(requestVo.getActType()) && !StringUtils.hasText(examApply.getScheduledDate())) {
                logger.error("推送微信消息失败,没有查询到预约时间:{}", JSONUtil.toJsonStr(examApply));
                return "-1:推送微信消息失败,没有查询到预约时间";
            }

            HttpHeaders headers = new HttpHeaders();
            //  请勿轻易改变此提交方式，大部分的情况下，提交方式都是表单提交
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            // 患者id
            params.add("PatientId", examApply.getSickId());

            String wechatLinkUrl = appParamsConfig.getPushWechatLinkUrl()
                    .replace("{PatientId}", examApply.getSickId());

            // wechatLinkUrl += "?commonCard=" + examApply.getSickId();
            if ("0,1".contains(requestVo.getActType())) {
                String wechatTitle = appParamsConfig.getPushWechatTitle();

                String scheduledDate = examApply.getScheduledDate() + " " + examApply.getScheduleApm();

                if (!StringUtils.hasText(scheduledDate)) {
                    scheduledDate = "--";
                }

                wechatTitle = wechatTitle.replace("{PatientName}", examApply.getName());
                wechatTitle = wechatTitle.replace("{ExamTime}", scheduledDate);
                wechatTitle = wechatTitle.replace("{ExamClass}", examApply.getExamClass());
                params.add("Title", wechatTitle);

                // 检查时间：{ExamTime} \r\n 检查地址：{ExamAddress} \r\n 检查类别：{ExamClass} \r\n 预约内容：{ExamSubClass} \r\n 注意事项：请按照预约时间提前半小时到放射科自助机上报到排队
                String template = appParamsConfig.getPushWechatMsg();

                template = template.replace("{ExamTime}", scheduledDate);
                template = template.replace("{ExamClass}", examApply.getExamClass());

                if (StringUtils.hasText(examApply.getLocation())) {
                    template = template.replace("{ExamAddress}", examApply.getLocation());
                } else {
                    template = template.replace("{ExamAddress}", "--");
                }


                if (StringUtils.hasText(examApply.getItemName())) {
                    template = template.replace("{ExamSubClass}", examApply.getItemName());
                } else {
                    template = template.replace("{ExamSubClass}", "--");
                }

                template = template.replace("/n", URLDecoder.decode("%0A", Charset.defaultCharset()));
                params.add("Msg", template);

                params.add("Remark", "");
                params.add("Date", scheduledDate);

                params.add("HisUrl", wechatLinkUrl);

            } else if ("2".contains(requestVo.getActType())) {
                // 取消预约
                String wechatTitle = appParamsConfig.getPushWechatCancelTitle();
                wechatTitle = wechatTitle.replace("{PatientName}", examApply.getName());
                wechatTitle = wechatTitle.replace("{ExamClass}", examApply.getExamClass());

                params.add("Title", wechatTitle);

                String msgTemplate = appParamsConfig.getPushWechatCancelMsg();
                msgTemplate = msgTemplate.replace("{ExamClass}", examApply.getExamClass());
                if (StringUtils.hasText(examApply.getItemName())) {
                    msgTemplate = msgTemplate.replace("{ExamSubClass}", examApply.getItemName());
                } else {
                    msgTemplate = msgTemplate.replace("{ExamSubClass}", "--");
                }
                msgTemplate = msgTemplate.replace("/n", URLDecoder.decode("%0A", Charset.defaultCharset()));

                params.add("Msg", msgTemplate);
                params.add("Remark", "");
                params.add("Date", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
                params.add("HisUrl", wechatLinkUrl);
            }

            // logger.info("微信请求参数:url：{}，{}", appParamsConfig.getPushWechatUrl(), JSONUtil.toJsonStr(params));

            HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
            //  执行HTTP请求

            ResponseEntity<String> response = restTemplate.postForEntity(appParamsConfig.getPushWechatUrl(), requestEntity, String.class);
            // logger.info("微信消息返回结果:{}", JSONUtil.toJsonStr(response));

            // 解析返回结果
            JAXBContext context = JAXBContext.newInstance(WechatNoticeResponse.class);
            StringReader reader = new StringReader(response.getBody());
            SAXParserFactory sax = SAXParserFactory.newInstance();
            sax.setNamespaceAware(false); // 忽略命名空间
            XMLReader xmlReader = sax.newSAXParser().getXMLReader();
            Source source = new SAXSource(xmlReader, new InputSource(reader));
            Unmarshaller unmarshaller = context.createUnmarshaller();
            WechatNoticeResponse noticeResponse = (WechatNoticeResponse) unmarshaller.unmarshal(source);

            if (noticeResponse.getBody().getSendNoticeResponse().getSendNoticeResult().equals("0")) {
                // logger.info("pushWechatMsg:微信消息推送成功！" + JSONUtil.toJsonStr(params));
                return "0";
            }
            logger.info("pushWechatMsg:微信消息推送失败！url" + appParamsConfig.getPushWechatUrl()
                    + JSONUtil.toJsonStr(params));
            return "-1:" + noticeResponse.getBody().getSendNoticeResponse().getSendNoticeResult();
        } catch (Exception e) {
            logger.error("pushWechatMsg:url:" + appParamsConfig.getPushWechatUrl()
                            + ",params:" + JSONUtil.toJsonStr(params),
                    e);
            return "-1:" + e.getMessage();
        }
    }


    /**
     * 监听队列消息
     *
     * @param queueManageName
     * @param queueName
     * @param systemCode
     * @param callBack
     * @return
     */
    public synchronized boolean listenReceiveMessage(String queueManageName, String queueName, String systemCode, Consumer<MessageEntity> callBack) {
        String key = String.format("%s_%s_%s", queueManageName, queueName, systemCode);
        if (Objects.isNull(executor)) {
            executor = new ThreadPoolExecutor(4, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        }
        try {
            if (!listenMap.containsKey(key)) {
                MessageHandler messageHandler = new MessageHandler(executor) {
                    @Override
                    public void onMessage(MessageEntity messageEntity) throws Throwable {
                        try {
                            // 使用线程池处理消息
                            executor.execute(() -> {
                                try {
                                    callBack.accept(messageEntity);
                                } catch (Exception e) {
                                    logger.error("消息处理失败:" + JSONUtil.toJsonStr(messageEntity), e);
                                }
                            });
                        } catch (Exception e) {
                            logger.error("消息处理失败:" + JSONUtil.toJsonStr(messageEntity), e);
                        }
                    }

                    @Override
                    public void onException(Throwable throwable, MessageEntity messageEntity) {
                        logger.error("接收消息异常:" + queueManageName + "---" + queueName + "  " + JSONUtil.toJsonStr(messageEntity), throwable);
                    }
                };
                logger.info("listenReceiveMessage:{}", key);
                ewellServiceTool.buildTopicListener(queueManageName, queueName, messageHandler);
                listenMap.put(key, messageHandler);
            }
            return true;
        } catch (SDKException e) {
            logger.error("监听队列消息失败:" + key, e);
        }
        return false;
    }

    /**
     * 监听队列消息 自己处理异常消息
     *
     * @param queueManageName
     * @param queueName
     * @param systemCode
     * @param callback
     * @param errCallback
     * @return
     */
    public boolean listenReceiveMessage(String queueManageName, String queueName, String systemCode, Consumer<MessageEntity> callback, BiConsumer<Throwable, MessageEntity> errCallback) {
        String key = String.format("%s_%s_%s", queueManageName, queueName, systemCode);
        try {
            MessageHandler messageHandler = new MessageHandler() {
                @Override
                public void onMessage(MessageEntity messageEntity) throws Throwable {
                    try {
                        callback.accept(messageEntity);
                    } catch (Exception e) {
                        logger.error("消息处理失败:" + JSONUtil.toJsonStr(messageEntity), e);
                    }
                }

                @Override
                public void onException(Throwable throwable, MessageEntity messageEntity) {
                    logger.error("接收消息异常:" + JSONUtil.toJsonStr(messageEntity), throwable);
                    try {
                        errCallback.accept(throwable, messageEntity);
                    } catch (Exception e) {
                        logger.error("处理异常消息异常:" + JSONUtil.toJsonStr(messageEntity) + JSONUtil.toJsonStr(throwable), e);
                    }
                }
            };
            ewellServiceTool.buildTopicListener(queueManageName, queueName, systemCode, messageHandler);
            listenMap.put(key, messageHandler);
            return true;
        } catch (SDKException e) {
            logger.error("监听队列消息失败:" + key, e);
        }
        return false;
    }

    /**
     * 对象释放，销毁监听对象
     */
    protected void finalize() {
        try {
            if (!CollectionUtils.isEmpty(listenMap)) {
                for (String key : listenMap.keySet()) {
                    String[] config = key.split("_");
                    try {
                        ewellServiceTool.buildTopicListener(config[0], config[1], config[2], (MessageHandler) listenMap.get(key));
                    } catch (SDKException e) {
                    }
                }
            }
        } catch (Exception e) {
        }
    }


//    public ExamApplyDto getExamApplyInfo(ExamApplyDto dto) {
//
//        ExamApplyDto ea = getExamApplyInfoDb(dto);
//        List<ExamApplyItemDto> eais = getExamApplyItem(dto.getMergeNo(), ea);
//        String type = "SIGN".equals(dto.getType()) ? ConstantsUtil.ReminderType.SIGN.getType() : ConstantsUtil.ReminderType.SCHEDULE.getType();
//        List<String> reminders = getReminders(type, ea.getExamClass(), dto.getTerminalType());
//        //ExamApplyDto apply = getApplyForScheduleInfo(dto, ea, eais);

    ////        apply.setReminders(CommonUtil.convertNotice(reminders, ea.getPatientSource(), null, ea.getBirthDate()));
    ////        apply.setMemo(ea.getMemo());
    ////        apply.setChargeFlag(ea.getChargeFlag());
//        List<String> applyNoList = CommonUtil.mergeNo2ApplyNos(dto.getMergeNo());
//        if (applyNoList.size() > 1) {
//            List<String> reqMemoList = new ArrayList<String>();
//            if (StringUtils.isNotBlank(ea.getReqMemo())) {
//                reqMemoList.add(ea.getReqMemo());
//            }
//            for (String applyNo : applyNoList) {
//                if (applyNo.equals(dto.getApplyNo())) {
//                    continue;
//                }
//                ExamApplyDto examApply = new ExamApplyDto();
//                examApply.setApplyNo(applyNo);
//                ExamApplyDto examApplyDto = commonServiceImpl.getExamApplyInfo(examApply);
//                if (!StringUtils.isNotBlank(examApplyDto.getReqMemo())) {
//                    continue;
//                }
//                if (reqMemoList.contains(examApplyDto.getReqMemo())) {
//                    continue;
//                }
//                reqMemoList.add(examApplyDto.getReqMemo());
//            }
//            if (reqMemoList != null && reqMemoList.size() > 0) {
//                ea.setReqMemo(String.join(",", reqMemoList));
//            }
//        }
//        return apply;
//    }
    public ExamApplyDto getExamApplyInfoDb(ExamApplyDto dto) {
        ExamApplyDto ea = null;
        if (dto.getApplyNo().startsWith("RE")) {
            ea = examReferMapper.findByApplyNo(dto.getApplyNo());
        } else {
            ea = examApplyMapper.getExamApply(dto);
        }
        return ea;
    }


    public List<ExamApplyItemDto> getExamApplyItem(String mergeNo, ExamApplyDto ea) {
        ExamApplyItemDto examApplyItemDto = new ExamApplyItemDto();
        boolean isRe = false;
        if (StringUtils.hasText(mergeNo)) {
            String[] mergeNos = mergeNo.split(",");
            List<String> applyNos = new ArrayList<String>();
            for (String str : mergeNos) {
                int idx = str.lastIndexOf("-");
                String applyno = str.substring(0, idx);
                if (applyNos.size() == 0 && applyno.startsWith("RE")) {
                    isRe = true;
                }
                applyNos.add(applyno);
            }
            examApplyItemDto.setApplyNos(applyNos);
        } else {
            if (ea.getApplyNo().startsWith("RE")) {
                isRe = true;
            }
            examApplyItemDto.setApplyNo(ea.getApplyNo());
        }
        List<ExamApplyItemDto> items = new ArrayList<ExamApplyItemDto>();
        if (isRe) {
            items = examApplyItemMapper.getReItemList(examApplyItemDto);
        } else {
            items = examApplyItemMapper.getList(examApplyItemDto);
        }
        List<ExamApplyItemDto> newItems = new ArrayList<ExamApplyItemDto>();
        for (ExamApplyItemDto eai : items) {
            newItems.add(eai);
        }
        if (newItems != null && newItems.size() > 0) {
            List<String> list = CommonUtil.split2List(mergeNo);
            Iterator<ExamApplyItemDto> it = newItems.iterator();
            while (it.hasNext()) {
                ExamApplyItemDto item = it.next();
                String mergeNos2 = item.getApplyNo() + "-" + item.getItemNo();
                if (list != null && list.size() > 0 && !list.contains(mergeNos2)) {
                    it.remove();
                }
            }
        }
        // handItemCode(newItems, ea.getExamSubClass());
        return newItems;
    }

}
